// import com.alibaba.druid.sql.parser.*;

// import java.util.HashMap;
// import java.util.Map;

// import static com.alibaba.druid.sql.parser.CharTypes.isIdentifierChar;
// import static com.alibaba.druid.sql.parser.LayoutCharacters.EOI;

#include "OracleLexer.h"
#include "../../../parser/Keywords.h"
#include "../../../parser/ParserException.h"
#include "../../../parser/NotAllowCommentException.h"
#include "../../../parser/CharTypes.h"
#include "../../../parser/LayoutCharacters.h"
#include "../../../../Exception/IllegalStateException.h"
#include "../../../../utils/BOOL.h"

std::shared_ptr<std::unordered_map<std::string, Token_ptr>> OracleLexer::map_ =
    std::make_shared<std::unordered_map<std::string, Token_ptr>>();

BOOL_ptr OracleLexer::__init = OracleLexer::init();
BOOL_ptr OracleLexer::init()
{
  // Map<string_ptr, Token> map = new HashMap<string_ptr, Token>();

  // map.putAll(Keywords.DEFAULT_KEYWORDS.getKeywords());
  OracleLexer::map_->insert(Keywords::DEFAULT_KEYWORDS->getKeywords()->begin(), Keywords::DEFAULT_KEYWORDS->getKeywords()->end());

  OracleLexer::map_->insert(std::make_pair("BEGIN", Token_ptr(new Token(&Token::BEGIN))));
  OracleLexer::map_->insert(std::make_pair("COMMENT", Token_ptr(new Token(&Token::COMMENT))));
  OracleLexer::map_->insert(std::make_pair("COMMIT", Token_ptr(new Token(&Token::COMMIT))));
  OracleLexer::map_->insert(std::make_pair("CONNECT", Token_ptr(new Token(&Token::CONNECT))));
  OracleLexer::map_->insert(std::make_pair("CONTINUE", Token_ptr(new Token(&Token::CONTINUE))));
  OracleLexer::map_->insert(std::make_pair("CROSS", Token_ptr(new Token(&Token::CROSS))));
  OracleLexer::map_->insert(std::make_pair("CURSOR", Token_ptr(new Token(&Token::CURSOR))));
  OracleLexer::map_->insert(std::make_pair("DECLARE", Token_ptr(new Token(&Token::DECLARE))));
  OracleLexer::map_->insert(std::make_pair("ERRORS", Token_ptr(new Token(&Token::ERRORS))));
  OracleLexer::map_->insert(std::make_pair("EXCEPTION", Token_ptr(new Token(&Token::EXCEPTION))));
  OracleLexer::map_->insert(std::make_pair("EXCLUSIVE", Token_ptr(new Token(&Token::EXCLUSIVE))));
  OracleLexer::map_->insert(std::make_pair("EXTRACT", Token_ptr(new Token(&Token::EXTRACT))));
  OracleLexer::map_->insert(std::make_pair("GOTO", Token_ptr(new Token(&Token::GOTO))));
  OracleLexer::map_->insert(std::make_pair("IF", Token_ptr(new Token(&Token::IF))));
  OracleLexer::map_->insert(std::make_pair("ELSIF", Token_ptr(new Token(&Token::ELSIF))));
  OracleLexer::map_->insert(std::make_pair("LIMIT", Token_ptr(new Token(&Token::LIMIT))));
  OracleLexer::map_->insert(std::make_pair("LOOP", Token_ptr(new Token(&Token::LOOP))));
  OracleLexer::map_->insert(std::make_pair("MATCHED", Token_ptr(new Token(&Token::MATCHED))));
  OracleLexer::map_->insert(std::make_pair("MERGE", Token_ptr(new Token(&Token::MERGE))));
  OracleLexer::map_->insert(std::make_pair("MODE", Token_ptr(new Token(&Token::MODE))));
  OracleLexer::map_->insert(std::make_pair("NOWAIT", Token_ptr(new Token(&Token::NOWAIT))));
  OracleLexer::map_->insert(std::make_pair("OF", Token_ptr(new Token(&Token::OF))));
  OracleLexer::map_->insert(std::make_pair("PRIOR", Token_ptr(new Token(&Token::PRIOR))));
  OracleLexer::map_->insert(std::make_pair("REJECT", Token_ptr(new Token(&Token::REJECT))));
  OracleLexer::map_->insert(std::make_pair("RETURN", Token_ptr(new Token(&Token::RETURN))));
  OracleLexer::map_->insert(std::make_pair("RETURNING", Token_ptr(new Token(&Token::RETURNING))));
  OracleLexer::map_->insert(std::make_pair("SAVEPOINT", Token_ptr(new Token(&Token::SAVEPOINT))));
  OracleLexer::map_->insert(std::make_pair("SESSION", Token_ptr(new Token(&Token::SESSION))));
  OracleLexer::map_->insert(std::make_pair("SHARE", Token_ptr(new Token(&Token::SHARE))));
  OracleLexer::map_->insert(std::make_pair("START", Token_ptr(new Token(&Token::START))));
  OracleLexer::map_->insert(std::make_pair("SYSDATE", Token_ptr(new Token(&Token::SYSDATE))));
  OracleLexer::map_->insert(std::make_pair("UNLIMITED", Token_ptr(new Token(&Token::UNLIMITED))));
  OracleLexer::map_->insert(std::make_pair("USING", Token_ptr(new Token(&Token::USING))));
  OracleLexer::map_->insert(std::make_pair("WAIT", Token_ptr(new Token(&Token::WAIT))));
  OracleLexer::map_->insert(std::make_pair("WITH", Token_ptr(new Token(&Token::WITH))));
  OracleLexer::map_->insert(std::make_pair("PCTFREE", Token_ptr(new Token(&Token::PCTFREE))));
  OracleLexer::map_->insert(std::make_pair("INITRANS", Token_ptr(new Token(&Token::INITRANS))));
  OracleLexer::map_->insert(std::make_pair("MAXTRANS", Token_ptr(new Token(&Token::MAXTRANS))));
  OracleLexer::map_->insert(std::make_pair("SEGMENT", Token_ptr(new Token(&Token::SEGMENT))));
  OracleLexer::map_->insert(std::make_pair("CREATION", Token_ptr(new Token(&Token::CREATION))));
  OracleLexer::map_->insert(std::make_pair("IMMEDIATE", Token_ptr(new Token(&Token::IMMEDIATE))));
  OracleLexer::map_->insert(std::make_pair("DEFERRED", Token_ptr(new Token(&Token::DEFERRED))));
  OracleLexer::map_->insert(std::make_pair("STORAGE", Token_ptr(new Token(&Token::STORAGE))));
  OracleLexer::map_->insert(std::make_pair("NEXT", Token_ptr(new Token(&Token::NEXT))));
  OracleLexer::map_->insert(std::make_pair("MINEXTENTS", Token_ptr(new Token(&Token::MINEXTENTS))));
  OracleLexer::map_->insert(std::make_pair("MAXEXTENTS", Token_ptr(new Token(&Token::MAXEXTENTS))));
  OracleLexer::map_->insert(std::make_pair("MAXSIZE", Token_ptr(new Token(&Token::MAXSIZE))));
  OracleLexer::map_->insert(std::make_pair("PCTINCREASE", Token_ptr(new Token(&Token::PCTINCREASE))));
  OracleLexer::map_->insert(std::make_pair("FLASH_CACHE", Token_ptr(new Token(&Token::FLASH_CACHE))));
  OracleLexer::map_->insert(std::make_pair("CELL_FLASH_CACHE", Token_ptr(new Token(&Token::CELL_FLASH_CACHE))));
  OracleLexer::map_->insert(std::make_pair("NONE", Token_ptr(new Token(&Token::NONE))));
  OracleLexer::map_->insert(std::make_pair("LOB", Token_ptr(new Token(&Token::LOB))));
  OracleLexer::map_->insert(std::make_pair("STORE", Token_ptr(new Token(&Token::STORE))));
  OracleLexer::map_->insert(std::make_pair("ROW", Token_ptr(new Token(&Token::ROW))));
  OracleLexer::map_->insert(std::make_pair("CHUNK", Token_ptr(new Token(&Token::CHUNK))));
  OracleLexer::map_->insert(std::make_pair("CACHE", Token_ptr(new Token(&Token::CACHE))));
  OracleLexer::map_->insert(std::make_pair("NOCACHE", Token_ptr(new Token(&Token::NOCACHE))));
  OracleLexer::map_->insert(std::make_pair("LOGGING", Token_ptr(new Token(&Token::LOGGING))));
  OracleLexer::map_->insert(std::make_pair("NOCOMPRESS", Token_ptr(new Token(&Token::NOCOMPRESS))));
  OracleLexer::map_->insert(std::make_pair("KEEP_DUPLICATES", Token_ptr(new Token(&Token::KEEP_DUPLICATES))));
  OracleLexer::map_->insert(std::make_pair("EXCEPTIONS", Token_ptr(new Token(&Token::EXCEPTIONS))));
  OracleLexer::map_->insert(std::make_pair("PURGE", Token_ptr(new Token(&Token::PURGE))));
  OracleLexer::map_->insert(std::make_pair("INITIALLY", Token_ptr(new Token(&Token::INITIALLY))));
  OracleLexer::map_->insert(std::make_pair("FETCH", Token_ptr(new Token(&Token::FETCH))));
  OracleLexer::map_->insert(std::make_pair("TABLESPACE", Token_ptr(new Token(&Token::TABLESPACE))));
  OracleLexer::map_->insert(std::make_pair("PARTITION", Token_ptr(new Token(&Token::PARTITION))));
  OracleLexer::map_->insert(std::make_pair("TRUE", Token_ptr(new Token(&Token::XTRUE))));
  OracleLexer::map_->insert(std::make_pair("FALSE", Token_ptr(new Token(&Token::XFALSE))));
  OracleLexer::map_->insert(std::make_pair("CASCADE", Token_ptr(new Token(&Token::CASCADE))));
  OracleLexer::map_->insert(std::make_pair("，", Token_ptr(new Token(&Token::COMMA))));
  OracleLexer::map_->insert(std::make_pair("（", Token_ptr(new Token(&Token::LPAREN))));
  OracleLexer::map_->insert(std::make_pair("）", Token_ptr(new Token(&Token::RPAREN))));

  // DEFAULT_ORACLE_KEYWORDS = new Keywords(map);
}
Keywords_ptr OracleLexer::DEFAULT_ORACLE_KEYWORDS = std::shared_ptr<Keywords>(new Keywords(map_));

OracleLexer::OracleLexer(char *input, int inputLength, BOOL_ptr skipComment)
    : Lexer(const_cast<wchar_t *>(encode_util::UTF8ToUnicode(input).c_str()), inputLength, skipComment)
{

  Lexer::keywords = DEFAULT_ORACLE_KEYWORDS;
}

OracleLexer::OracleLexer(string_ptr input)
    : Lexer(encode_util::UTF8ToUnicode(*input))
{

  this->skipComment = BOOL::TRUE;
  this->keepComments = BOOL::TRUE;
  Lexer::keywords = DEFAULT_ORACLE_KEYWORDS;
}

OracleLexer::OracleLexer(string_ptr input, SQLParserFeature_list_ptr features)
    : Lexer(encode_util::UTF8ToUnicode(*input))
{
  this->skipComment = BOOL::TRUE;
  this->keepComments = BOOL::TRUE;
  Lexer::keywords = DEFAULT_ORACLE_KEYWORDS;

  for (SQLParserFeature_ptr feature : *features)
  {
    config(feature, BOOL::TRUE);
  }
}

void OracleLexer::scanVariable()
{
  wchar_t c0 = ch;
  if (c0 != L':' && c0 != L'#' && c0 != L'$')
  {
    std::string tmp = "illegal variable. ";
    tmp += encode_util::UnicodeToUTF8(info()).c_str();
    throw new ParserException(make_string_ptr(tmp));
  }

  mark_ = pos_;
  bufPos = 1;
  wchar_t ch;

  BOOL_ptr quoteFlag = BOOL::FALSE;
  BOOL_ptr mybatisFlag = BOOL::FALSE;

  wchar_t c1 = charAt(pos_ + 1);
  if (c0 == L':' && c1 == L' ')
  {
    pos_++;
    bufPos = 2;
    c1 = charAt(pos_ + 1);
  }

  if (c1 == L'"')
  {
    pos_++;
    bufPos++;
    quoteFlag = BOOL::TRUE;
  }
  else if (c1 == L'{')
  {
    pos_++;
    bufPos++;
    mybatisFlag = BOOL::TRUE;
  }

  if (c0 == L':' && c1 >= L'0' && c1 <= L'9')
  {
    for (;;)
    {
      ch = charAt(++pos_);

      if (ch < L'0' || ch > L'9')
      {
        break;
      }

      bufPos++;
      continue;
    }
  }
  else
  {
    for (;;)
    {
      ch = charAt(++pos_);

      if (!CharTypes::isIdentifierChar(ch) && ch != L':')
      {
        break;
      }

      bufPos++;
      continue;
    }
  }

  if (quoteFlag)
  {
    if (ch != L'"')
    {
      std::string tmp = "syntax error. ";
      tmp += encode_util::UnicodeToUTF8(info()).c_str();
      throw new ParserException(make_string_ptr(tmp));
    }
    ++pos_;
    bufPos++;
  }
  else if (mybatisFlag)
  {
    if (ch != L'}')
    {
      std::string tmp = "syntax error. ";
      tmp += encode_util::UnicodeToUTF8(info()).c_str();
      throw new ParserException(make_string_ptr(tmp));
    }
    ++pos_;
    bufPos++;
  }

  this->ch = charAt(pos_);

  stringVal_ = addSymbol();
  Token_ptr tok = keywords->getKeyword(encode_util::UnicodeToUTF8(stringVal_).c_str());
  if (tok != nullptr)
  {
    token_ = tok;
  }
  else
  {
    token_ = Token_ptr(new Token(&Token::VARIANT));
  }
}

void OracleLexer::scanVariable_at()
{
  scanChar();

  if (ch == L'@')
  {
    scanChar();
    token_ = Token_ptr(new Token(&Token::MONKEYS_AT_AT));
  }
  else
  {
    token_ = Token_ptr(new Token(&Token::MONKEYS_AT));
  }
  return;
}

void OracleLexer::scanComment()
{
  if (ch != L'/' && ch != L'-')
  {
    throw new IllegalStateException();
  }

  mark_ = pos_;
  bufPos = 0;
  scanChar();

  // /*+ */
  if (ch == L'*')
  {
    scanChar();
    bufPos++;

    while (ch == L' ')
    {
      scanChar();
      bufPos++;
    }

    BOOL_ptr isHint = BOOL::FALSE;
    int startHintSp = bufPos + 1;
    if (ch == L'+')
    {
      isHint = BOOL::TRUE;
      scanChar();
      bufPos++;
    }

    for (; !isEOF();)
    {
      if (ch == L'*' && charAt(pos_ + 1) == L'/')
      {
        bufPos += 2;
        scanChar();
        scanChar();
        break;
      }

      scanChar();
      bufPos++;
    }

    if (isHint)
    {
      stringVal_ = subString(mark_ + startHintSp, (bufPos - startHintSp) - 1);
      token_ = Token_ptr(new Token(&Token::HINT));
    }
    else
    {
      stringVal_ = subString(mark_, bufPos + 1);
      token_ = Token_ptr(new Token(&Token::MULTI_LINE_COMMENT));
      commentCount++;
      if (keepComments)
      {
        addComment(stringVal_);
      }
    }

    if (token_->name->c_str() != Token::HINT.name->c_str() && !isAllowComment())
    {
      throw new NotAllowCommentException();
    }

    return;
  }

  if (!isAllowComment())
  {
    throw new NotAllowCommentException();
  }

  if (ch == L'/' || ch == L'-')
  {
    scanChar();
    bufPos++;

    for (;;)
    {
      if (ch == L'\r')
      {
        if (charAt(pos_ + 1) == L'\n')
        {
          bufPos += 2;
          scanChar();
          break;
        }
        bufPos++;
        break;
      }
      else if (ch == LayoutCharacters::EOI)
      {
        break;
      }

      if (ch == L'\n')
      {
        scanChar();
        bufPos++;
        break;
      }

      scanChar();
      bufPos++;
    }

    stringVal_ = subString(mark_, ch != LayoutCharacters::EOI ? bufPos : bufPos + 1);
    token_ = Token_ptr(new Token(&Token::LINE_COMMENT));
    commentCount++;
    if (keepComments)
    {
      addComment(stringVal_);
    }
    endOfComment = isEOF() ? BOOL::TRUE : BOOL::FALSE;
    return;
  }
}

void OracleLexer::scanNumber()
{
  mark_ = pos_;

  if (ch == L'-')
  {
    bufPos++;
    ch = charAt(++pos_);
  }

  for (;;)
  {
    if (ch >= L'0' && ch <= L'9')
    {
      bufPos++;
    }
    else
    {
      break;
    }
    ch = charAt(++pos_);
  }

  BOOL_ptr isDouble = BOOL::FALSE;

  if (ch == L'.')
  {
    if (charAt(pos_ + 1) == L'.')
    {
      token_ = Token_ptr(new Token(&Token::LITERAL_INT));
      return;
    }
    bufPos++;
    ch = charAt(++pos_);
    isDouble = BOOL::TRUE;

    for (;;)
    {
      if (ch >= L'0' && ch <= L'9')
      {
        bufPos++;
      }
      else
      {
        break;
      }
      ch = charAt(++pos_);
    }
  }

  if ((ch == L'e' || ch == L'E') && isDigit2(charAt(pos_ + 1)))
  {
    bufPos++;
    ch = charAt(++pos_);

    if (ch == L'+' || ch == L'-')
    {
      bufPos++;
      ch = charAt(++pos_);
    }

    for (;;)
    {
      if (ch >= L'0' && ch <= L'9')
      {
        bufPos++;
      }
      else
      {
        break;
      }
      ch = charAt(++pos_);
    }

    isDouble = BOOL::TRUE;
  }

  if (ch == L'f' || ch == L'F')
  {
    token_ = Token_ptr(new Token(&Token::BINARY_FLOAT));
    scanChar();
    return;
  }

  if (ch == L'd' || ch == L'D')
  {
    token_ = Token_ptr(new Token(&Token::BINARY_DOUBLE));
    scanChar();
    return;
  }

  if (isDouble)
  {
    token_ = Token_ptr(new Token(&Token::LITERAL_FLOAT));
  }
  else
  {
    token_ = Token_ptr(new Token(&Token::LITERAL_INT));
  }
}
